﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data;
using System.Threading;

namespace AxeFrog.Data
{
	/// <summary>
	/// Handles storage of a reference to an <see cref="IDbConnection" /> object for persistence of a single
	/// connection across method boundaries. For a small standalone application, the storage would simply be
	/// a static variable. For a web environment, storage would be via the HttpContext.Items collection. Note
	/// that the connection should not be stored locally inside this class as multiple instances of this class
	/// should be able to access a common instance of the connection.
	/// </summary>
	public abstract class ConnectionHandle
	{
		/// <summary>
		/// Store the connection reference for later reference.
		/// </summary>
		/// <param name="conn">The connection reference to store.</param>
		public abstract void SaveConnection(IDbConnection conn, string name);
		/// <summary>
		/// Remove/clear any stored reference to the connection object.
		/// </summary>
		public abstract void ReleaseConnection(string name);
		/// <summary>
		/// Store a count that can be used by an external context object to track how many objects are still
		/// referencing this connection object.
		/// </summary>
		public abstract int GetConnectionUsageCount(string name);
		public abstract void SetConnectionUsageCount(string name, int value);
		/// <summary>
		/// Retrieve a reference to the stored connection object.
		/// </summary>
		/// <returns>The stored connection reference, or null if none exists.</returns>
		public abstract IDbConnection RetrieveConnection(string name);
		/// <summary>
		/// Store a count that can be used by an external context object to track how many objects still
		/// require access to an ambient transaction.
		/// </summary>
		public abstract int GetTransactionUsageCount(string name);
		public abstract void SetTransactionUsageCount(string name, int value);
		/// <summary>
		/// Default to false. Will be set to true by the connection context if a transactional block completes
		/// without an explicit call to Complete() on the context transaction object.
		/// </summary>
		public abstract bool IsTransactionCancelled(string name);
		public abstract void SetTransactionCancelled(string name, bool cancelled);

		public abstract void SaveTransaction(IDbTransaction trans, string name);
		public abstract IDbTransaction RetrieveTransaction(string name);
		public abstract void ReleaseTransaction(string name);
	}

	[Export(typeof(ConnectionHandle)), PartCreationPolicy(CreationPolicy.Shared)]
	public class DictionaryConnectionHandle : ConnectionHandle
	{
		private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();

		private const string ConnKey = "DictionaryConnectionHandler.IDbConnection.";
		private const string ConnCountKey = "DictionaryConnectionHandler.IDbConnection.UsageCount.";
		private const string TransKey = "DictionaryConnectionHandler.IDbTransaction.";
		private const string TransCountKey = "DictionaryConnectionHandler.IDbTransaction.UsageCount.";
		private const string TransCancelledKey = "DictionaryConnectionHandler.IDbTransaction.Cancelled.";

		private string ThreadPrefix
		{
			get { return Thread.CurrentThread.ManagedThreadId + "."; }
		}

		public override void SaveConnection(IDbConnection conn, string name)
		{
			lock(this)
				_dictionary[ThreadPrefix + ConnKey + name] = conn;
		}

		public override void ReleaseConnection(string name)
		{
			lock(this)
				_dictionary.Remove(ThreadPrefix + ConnKey + name);
		}

		public override int GetConnectionUsageCount(string name)
		{
			lock(this)
			{
				object count;
				return _dictionary.TryGetValue(ThreadPrefix + ConnCountKey + name, out count) ? (int)count : 0;
			}
		}

		public override void SetConnectionUsageCount(string name, int value)
		{
			lock(this)
				_dictionary[ThreadPrefix + ConnCountKey + name] = value;
		}

		public override IDbConnection RetrieveConnection(string name)
		{
			lock(this)
			{
				object conn;
				if(_dictionary.TryGetValue(ThreadPrefix + ConnKey + name, out conn))
					return (IDbConnection)conn;
				return null;
			}
		}

		public override int GetTransactionUsageCount(string name)
		{
			lock(this)
			{
				object count;
				return _dictionary.TryGetValue(ThreadPrefix + TransCountKey + name, out count) ? (int)count : 0;
			}
		}

		public override void SetTransactionUsageCount(string name, int value)
		{
			lock(this)
				_dictionary[ThreadPrefix + TransCountKey + name] = value;
		}

		public override bool IsTransactionCancelled(string name)
		{
			object cancelled;
			return _dictionary.TryGetValue(ThreadPrefix + TransCancelledKey + name, out cancelled) ? (bool?)cancelled ?? false : false;
		}

		public override void SetTransactionCancelled(string name, bool cancelled)
		{
			_dictionary[ThreadPrefix + TransCancelledKey + name] = cancelled;
		}

		public override void SaveTransaction(IDbTransaction trans, string name)
		{
			lock(this)
				_dictionary[ThreadPrefix + TransKey + name] = trans;
		}

		public override IDbTransaction RetrieveTransaction(string name)
		{
			lock(this)
			{
				object conn;
				if(_dictionary.TryGetValue(ThreadPrefix + TransKey + name, out conn))
					return (IDbTransaction)conn;
				return null;
			}
		}

		public override void ReleaseTransaction(string name)
		{
			lock(this)
				_dictionary.Remove(ThreadPrefix + TransKey + name);
		}
	}
}